//@author A0081058H
package net.mysoc.w111j.ui.expensedetail;

import static net.mysoc.w111j.ui.Constants.DATE_FORMAT;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.util.converter.DateStringConverter;
import net.mysoc.w111j.model.Expense;
import net.mysoc.w111j.model.Money;
import net.mysoc.w111j.model.User;
import net.mysoc.w111j.service.ExpenseService;
import net.mysoc.w111j.ui.main.MainPresenter;
import net.mysoc.w111j.ui.validation.Validation;
import net.mysoc.w111j.ui.validation.ValidationMessage;
import net.mysoc.w111j.ui.validation.ValidationState;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The presenter for the expense detail window. For now, this also stores its own {@code Stage} (representing the dialog
 * window)
 */
public class ExpenseDetailPresenter {
    private static Logger logger = LoggerFactory.getLogger(ExpenseDetailPresenter.class);

    private MainPresenter mainPresenter;
    private final ExpenseDetailModel model;
    private final ExpenseDetailView view;
    private Stage dialogStage;
    private ExpenseService expenseService;

    /**
     * Constructor. Links the {@code ExpenseDetailModel} and {@code ExpenseDetailView} to this {@code ExpenseDetailView}
     * .
     *
     * @param model
     *        the {@code ExpenseDetailModel} to link to this {@code ExpenseDetailPresenter}
     * @param view
     *        the {@code ExpenseDetailView} to link to this {@code ExpenseDetailView}
     */
    public ExpenseDetailPresenter(ExpenseDetailModel model, ExpenseDetailView view) {
        this.model = model;
        this.view = view;
        this.view.setPresenter(this);
    }

    public void setExpenseService(ExpenseService expenseService) {
        this.expenseService = expenseService;
    }

    public void setMainPresenter(MainPresenter mainPresenter) {
        this.mainPresenter = mainPresenter;
    }

    /**
     * Returns the associated {@code ExpenseDetailView} with this {@code ExpenseDetailPresenter}
     *
     * @return the associated {@code ExpenseDetailView} with this {@code ExpenseDetailPresenter}
     */
    public ExpenseDetailView getView() {
        return this.view;
    }

    /**
     * Passes input from {@code ExpenseDetailModel} to the relevant service and clears the model.
     */
    public void handleOK() {
        logger.debug("handleOK: {}", this.model.getPrice());
        Expense updatedExpense = new Expense(this.model.getId(), this.model.getName(), this.model.getDetails(),
                new Money(this.model.getPrice()), this.model.getCategory(), this.model.getPaymentType(),
                new DateStringConverter(DATE_FORMAT).fromString(this.model.getDate()));
        this.expenseService.updateExpense(updatedExpense);
        this.mainPresenter.refreshAll();
    }

    /**
     * Creates, if it does not exist, the dialog window and shows it.
     *
     * @param parentStage
     *        the parent {@code Stage} for this window.
     */
    public void showDialog(Stage parentStage) {
        if (this.dialogStage == null) {
            this.dialogStage = new Stage();
            this.dialogStage.setResizable(false);
            this.dialogStage.initModality(Modality.WINDOW_MODAL);
            this.dialogStage.initOwner(parentStage);
            this.dialogStage.setTitle("EzXpns - Expense Details");
            this.dialogStage.getIcons().add(new Image("ezicon.png"));
            Scene scene = new Scene(getView());
            scene.getStylesheets().add("ezxpns.css");
            this.dialogStage.setScene(scene);
        }

        dialogStage.show();
    }

    /**
     * Populates the associated {@code ExpenseDetailModel} with information from the given {@code Expense}.
     *
     * @param expense
     *        the {@code Expense} to take details from. Pass {@code null} when adding a new expense.
     */
    public void setExpense(Expense expense) {
        if (expense == null) {
            this.model.setId(User.NO_ID);
            this.model.setName("");
            this.model.setPrice("");
            // Insert today's date for convenience
            this.model.setDate(DATE_FORMAT.format(new Date()));
            this.model.setCategory(expenseService.getAllCategories().get(0));
            this.model.setCategories(expenseService.getAllCategories());
            this.model.setDetails("");
            this.model.setPaymentTypes(expenseService.getAllPaymentTypes());
            this.model.setPaymentType(expenseService.getAllPaymentTypes().get(0));
        } else {
            this.model.setId(expense.getId());
            this.model.setName(expense.getName());
            this.model.setPrice(expense.getPrice().toPlainString());
            this.model.setDate(DATE_FORMAT.format(expense.getDate()));
            this.model.setCategory(expense.getCategory());
            this.model.setCategories(expenseService.getAllCategories());
            this.model.setDetails(expense.getDetails());
            this.model.setPaymentTypes(expenseService.getAllPaymentTypes());
            this.model.setPaymentType(expense.getPaymentType());
        }
    }

    /**
     * Checks if all fields which require validation pass their validation checks.
     *
     * @return whether fields which require validation pass their validation checks. Warnings are considered fine.
     */
    public boolean validateInput() {
        List<ValidationMessage> validatedResults = new LinkedList<>();

        validatedResults.add(validateDate());
        validatedResults.add(validateName());
        validatedResults.add(validatePrice());

        for (ValidationMessage validationMessage : validatedResults) {
            if (validationMessage.getState() == ValidationState.ERROR) {
                return false;
            }
        }

        return true;
    }

    /**
     * Validates date field using the Validation class.
     *
     * @return a {@code ValidationMessage} representing validation state.
     */
    public ValidationMessage validateDate() {
        return Validation.validateDate(this.model.getDate());
    }

    /**
     * Validates name field using the Validation class.
     *
     * @return a {@code ValidationMessage} representing validation state.
     */
    public ValidationMessage validateName() {
        return Validation.validateRequired(this.model.getName());
    }

    /**
     * Validates price field, checks for valid price.
     *
     * @return a {@code ValidationMessage} representing validation state.
     */
    public ValidationMessage validatePrice() {
        return Validation.validateMoney(this.model.getPrice());
    }
}
